package proxy

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"strings"
	"time"

	"gopin/config"
	"gopin/logger"

	"github.com/pkg/errors"
	"github.com/wailsapp/wails/v2/pkg/runtime"
)

var (
	tunnelConnectionEstablished = []byte("HTTP/1.1 200 Connection Established\r\n\r\n")
	internalServerErr           = []byte(fmt.Sprintf("HTTP/1.1 %d %s\r\n\r\n", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)))
	hopToHopHeader              = []string{
		"Keep-Alive",
		"Transfer-Encoding",
		"TE",
		"Connection",
		"Trailer",
		"Upgrade",
		"Proxy-Authorization",
		"Proxy-Authenticate",
		"Connection",
	}
)

type Proxy struct {
	dns                *Dns
	server             *http.Server
	ctx                context.Context
	running            bool
	systemProxyEnabled bool
}

func (p *Proxy) Startup(ctx context.Context) {
	logger.Info("代理服务初始化")
	p.ctx = ctx
	p.running = false
	p.systemProxyEnabled = false

	// if config.GetInstance().IsAutoStart() {
	// 	go p.StartProxy()
	// }
}

func (p *Proxy) AddDnsRecord(host, record string) {
	p.dns.Add(map[string]string{host: record})
}

func (p *Proxy) GetProxyAddr() string {
	return config.GetInstance().GetProxyAddr()
}

func (p *Proxy) SetProxyAddr(addr string) {
	oldAddr := config.GetInstance().GetProxyAddr()
	config.GetInstance().SetProxyAddr(addr)

	if p.running {
		p.StopProxy()
		p.StartProxy()
	}

	if p.systemProxyEnabled && oldAddr != addr {
		p.SetSystemProxy()
	}
}

func (p *Proxy) IsSystemProxyEnabled() bool {
	return p.systemProxyEnabled
}

func (p *Proxy) IsProxyRunning() bool {
	return p.running
}

func (p *Proxy) SetProtocolFilter(protocols []string) {
	config.GetInstance().SetProtocolFilters(protocols)
}

func (p *Proxy) SetContentTypeFilter(types []string) {
	config.GetInstance().SetContentTypeFilters(types)
}

func (p *Proxy) AddFilterDomain(domain string) {
	if err := config.GetInstance().AddFilterDomain(domain); err == nil {
		runtime.EventsEmit(p.ctx, "proxy_filter", fmt.Sprintf("Add domain filter: %s", domain))
	}
}

func (p *Proxy) CleanFilterDomains() {
	if err := config.GetInstance().CleanFilterDomains(); err == nil {
		runtime.EventsEmit(p.ctx, "proxy_filter", "Clean domain filters")
	}
}

func (p *Proxy) StartProxy() error {
	p.dns = &DefaultDns
	p.server = &http.Server{
		Addr: config.GetInstance().GetProxyAddr(),
		Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
			p.serverHandler(rw, req)
		}),
		ReadTimeout:  300 * time.Second,
		WriteTimeout: 300 * time.Second,
	}
	p.SetSystemProxy()
	addr := config.GetInstance().GetProxyAddr()
	runtime.EventsEmit(p.ctx, "proxy_start", "Proxy Listen On "+addr)
	p.running = true
	err := p.server.ListenAndServe()
	if err != nil {
		msg := fmt.Sprintf("Proxy Listen On %s Stopped, %s", addr, err.Error())
		p.UnsetSystemProxy()
		runtime.EventsEmit(p.ctx, "proxy_stop", msg)
		p.running = false
	}
	return err
}

func (p *Proxy) StopProxy() {
	p.server.Shutdown(context.Background())
}

func (p *Proxy) Shutdown(ctx context.Context) {
	if err := p.UnsetSystemProxy(); err != nil {
		logger.Error("关闭系统代理失败",
			"error", err,
		)
	}
	logger.Info("代理服务关闭")
}

func (p *Proxy) serverHandler(rw http.ResponseWriter, req *http.Request) {
	if req.URL.Hostname() == p.dns.SslCertHost && req.URL.Path == "/ssl" { // 安装移动端证书
		rw.Header().Add("Connection", "close")
		rw.Header().Add("Content-Type", "application/x-x509-ca-cert")
		rw.Write(GetCaCert())
		return
	}
	clientConn, err := hijackerConn(rw)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}
	// todo websocket
	switch req.Method {
	case http.MethodConnect: // https
		_, _ = clientConn.Write(tunnelConnectionEstablished)
		go p.handleHTTPS(clientConn, req)
	default:
		p.handleHTTP(clientConn, req)
	}
}

func hijackerConn(rw http.ResponseWriter) (net.Conn, error) {
	hijacker, ok := rw.(http.Hijacker)
	if !ok {
		return nil, http.ErrHijacked
	}
	conn, _, err := hijacker.Hijack()
	if err != nil {
		return nil, err
	}
	return conn, nil
}

func (p *Proxy) handleHTTPS(clientConn net.Conn, req *http.Request) {
	defer clientConn.Close()
	certificate, err := GetCertificate(req.URL.Host)
	if err != nil {
		p.errorEnd(clientConn, err)
		return
	}

	tlsConn := tls.Server(clientConn, &tls.Config{Certificates: []tls.Certificate{*certificate}})
	if err := tlsConn.Handshake(); err != nil {
		p.errorEnd(tlsConn, err)
		return
	}

	_ = tlsConn.SetDeadline(time.Now().Add(30 * time.Second))
	defer tlsConn.Close()

	proxyEntity, err := NewEntity(tlsConn)
	if err != nil {
		p.errorEnd(tlsConn, err)
		return
	}
	proxyEntity.SetScheme("https")
	proxyEntity.SetHost(req.URL.Host)
	proxyEntity.SetRemoteAddr(req.RemoteAddr)

	p.handleCommon(tlsConn, proxyEntity)
}

func (p *Proxy) handleHTTP(clientConn net.Conn, req *http.Request) {
	defer clientConn.Close()

	proxyEntity, err := newEntityWithRequest(req)
	if err != nil {
		p.errorEnd(clientConn, err)
		return
	}
	proxyEntity.SetScheme("http")

	p.handleCommon(clientConn, proxyEntity)
}

// handleCommon 处理 HTTP 和 HTTPS 的共同逻辑
func (p *Proxy) handleCommon(conn net.Conn, proxyEntity *HttpContext) {
	entity := proxyEntity.Entity
	if !strings.HasPrefix(entity.Url, "http") {
		entity.Url = entity.Scheme + "://" + entity.Host + entity.Url
	}

	// 检查是否匹配过滤规则
	if p.matchRequests(proxyEntity) {
		// 捕获原始请求
		rawReq, err := captureRawRequest(proxyEntity.Request)
		if err != nil {
			p.errorEnd(conn, err)
			return
		}
		entity.RawRequest = rawReq

		entity.Save()
		bytes, _ := json.Marshal(entity)
		runtime.EventsEmit(p.ctx, "new_proxy_request", string(bytes))

		resp, err := p.doRequest(proxyEntity)
		if err != nil {
			p.errorEnd(conn, err)
			return
		}
		defer resp.Body.Close()

		// 捕获原始响应
		rawResp, err := captureRawResponse(resp)
		if err != nil {
			p.errorEnd(conn, err)
			return
		}
		entity.RawResponse = rawResp

		if err = proxyEntity.SaveResponse(resp); err != nil {
			p.errorEnd(conn, err)
			return
		}

		bytes, _ = json.Marshal(proxyEntity.Entity)
		runtime.EventsEmit(p.ctx, "new_proxy_response", string(bytes))
		resp.Write(conn)
	} else {
		// 不匹配过滤规则时,直接转发请求而不记录和发送事件
		resp, err := p.doRequest(proxyEntity)
		if err != nil {
			p.errorEnd(conn, err)
			return
		}
		defer resp.Body.Close()
		resp.Write(conn)
	}
}

// 请求目标服务器
func (p *Proxy) doRequest(entity *HttpContext) (*http.Response, error) {
	removeHopHeader(entity.Request.Header)

	dialer := &net.Dialer{
		Timeout:  5 * time.Second,
		Deadline: time.Now().Add(30 * time.Second),
	}
	resp, err := (&http.Transport{
		DisableKeepAlives:     true,
		ResponseHeaderTimeout: 30 * time.Second,
		DialContext: func(ctx context.Context, network, addr string) (i net.Conn, e error) {
			addr, _ = p.dns.CustomDialer(addr)
			return dialer.DialContext(ctx, network, addr)
		},
	}).RoundTrip(entity.Request)
	if err != nil {
		return nil, err
	}
	removeHopHeader(resp.Header)
	return resp, nil
}

// remove hop header
func removeHopHeader(header http.Header) {
	for _, hop := range hopToHopHeader {
		if value := header.Get(hop); len(value) != 0 {
			if strings.EqualFold(hop, "Connection") {
				for _, customerHeader := range strings.Split(value, ",") {
					header.Del(strings.Trim(customerHeader, " "))
				}
			}
			header.Del(hop)
		}
	}
}

func (p *Proxy) errorEnd(net net.Conn, err error) {
	if err != nil {
		// 获取连接信息
		host := "unknown"
		url := "unknown"
		if tc, ok := net.(*tls.Conn); ok {
			if state := tc.ConnectionState(); state.HandshakeComplete {
				host = state.ServerName
			}
		}

		// 记录错误
		logger.Error("代理请求处理错误",
			"host", host,
			"url", url,
			"error", err,
			"stack", errors.WithStack(err),
		)

		// 向客户端写入错误响应
		_, _ = net.Write(internalServerErr)
		_, _ = net.Write([]byte(err.Error()))
	} else {
		_, _ = net.Write(internalServerErr)
	}
}

func (p *Proxy) matchRequests(proxyEntity *HttpContext) bool {
	domains := config.GetInstance().GetFilterDomains()
	if len(domains) > 0 {
		for _, domain := range domains {
			if domain == proxyEntity.Entity.Host {
				return p.matchSchemes(proxyEntity)
			}
		}
		return false
	}
	return p.matchSchemes(proxyEntity)
}

func (p *Proxy) matchSchemes(proxyEntity *HttpContext) bool {
	protocols := config.GetInstance().GetProtocolFilters()
	if len(protocols) == 0 {
		return false
	}
	scheme := strings.ToUpper(proxyEntity.Entity.Scheme)
	for _, protocol := range protocols {
		if protocol == scheme {
			return p.matchTypes(proxyEntity)
		}
	}
	return false
}

func (p *Proxy) matchTypes(proxyEntity *HttpContext) bool {
	contentTypes := config.GetInstance().GetContentTypeFilters()
	contentType := strings.ToLower(proxyEntity.Request.Header.Get("Accept"))
	if len(contentTypes) == 0 {
		return true
	}
	for _, typ := range contentTypes {
		switch strings.ToUpper(typ) {
		case "JSON":
			if strings.Contains(contentType, "application/json") {
				return true
			}
		case "FORM":
			if strings.Contains(contentType, "application/x-www-form-urlencoded") {
				return true
			}
		case "XML":
			if strings.Contains(contentType, "application/xml") ||
				strings.Contains(contentType, "text/xml") {
				return true
			}
		}
	}
	return false
}

// 添加一个新的工具函数来捕获原始请求
func captureRawRequest(req *http.Request) (string, error) {
	var rawReq strings.Builder

	// 写入请求行
	rawReq.WriteString(fmt.Sprintf("%s %s %s\r\n", req.Method, req.URL.RequestURI(), req.Proto))

	// 写入请求头
	for key, values := range req.Header {
		for _, value := range values {
			rawReq.WriteString(fmt.Sprintf("%s: %s\r\n", key, value))
		}
	}
	rawReq.WriteString("\r\n")

	// 如果有请求体，捕获请求体
	if req.Body != nil {
		bodyBytes, err := io.ReadAll(req.Body)
		if err != nil {
			return "", err
		}
		// 重要：重新设置请求体，因为ReadAll会消耗掉原始的请求体
		req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
		rawReq.Write(bodyBytes)
	}

	return rawReq.String(), nil
}

// 添加一个新的工具函数来捕获原始响应
func captureRawResponse(resp *http.Response) (string, error) {
	var rawResp strings.Builder

	// 写入状态行
	rawResp.WriteString(fmt.Sprintf("%s %s\r\n", resp.Proto, resp.Status))

	// 写入响应头
	for key, values := range resp.Header {
		for _, value := range values {
			rawResp.WriteString(fmt.Sprintf("%s: %s\r\n", key, value))
		}
	}
	rawResp.WriteString("\r\n")

	// 捕获响应体
	if resp.Body != nil {
		bodyBytes, err := io.ReadAll(resp.Body)
		if err != nil {
			return "", err
		}
		// 重要：重新设置响应体
		resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
		rawResp.Write(bodyBytes)
	}

	return rawResp.String(), nil
}
